package se.cbb.scripts;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;

import se.cbb.misc.SequenceFileWriter;

public class NewickToDistanceMatrix {

	public static void main(String[] args) {
		try {
			String path = "/Users/rhali/Documents/Projects/GrandOrthology/";

			String fileName = path + "Dist_matrix_Original.txt";
			BufferedReader bufferedreader = new BufferedReader(new FileReader(fileName));
			String str = bufferedreader.readLine();
			SequenceFileWriter.writeAndAppendLine(path, "Dist_Matrix_From_LCA.txt", str);
			while ((str = bufferedreader.readLine()) != null) {
				String[] strArr = str.split("\t");
				SequenceFileWriter.writeAndAppendString(path, "Dist_Matrix_From_LCA.txt", strArr[0]);
				for(int i = 1; i < strArr.length; i++) {
					String dist = strArr[i];
					Float distance = Float.parseFloat(dist)/2;
					SequenceFileWriter.writeAndAppendString(path, "Dist_Matrix_From_LCA.txt", "\t" + distance);
				}
				SequenceFileWriter.writeAndAppendString(path, "Dist_Matrix_From_LCA.txt", "\n");
			}
			bufferedreader.close();
		} catch (Exception e) {
			System.out.println("Error : " + e.getMessage());
		}

	}
	
	private static int pos=0;
	private static int depth=0;
	private static String treeStr = null;
	private static String token = null;
	
	private static HashMap<String, Integer> name2id;
	private static HashMap<Integer, String> id2name;

	private static char readChar() {
		char chr;

		// Indicate End Of String (EOS) 
		if(pos == treeStr.length())
			return '\0';

		chr = treeStr.charAt(pos); 
		pos++;

		// keep track of parent depth
		if (chr == '(') depth++;
		if (chr == ')') depth--;

		return chr;
	}

	private static char readUntil(String stops) {
		char chr;
		token = "";

		while (true) {
			chr = readChar();

			if (chr == '\0')
				return chr;

			// compare char to stop characters
			for (int i=0 ; i<stops.length() ;  i++) {
				if (chr == stops.charAt(i))
					return chr;
			}
			token += chr;
		}
	}

	private static Node readNewickNode(ArrayList<Node> tree, Node parent) {
		char char1;
		Node node;

		//Read first Character
		if( (char1 = readChar()) == '\0' ) {
			System.out.println("Error: Not a valid Newick tree string ...");
			return null;
		}

		if (char1 == '(') {
			// read internal node
			int depth2 = depth;
			node = new Node();
			tree.add(node);
			node.id = tree.size()-1;
			if (parent != null)
				parent.addChild(node);

			// read all child nodes at this depth
			while (depth == depth2) {
				Node child = readNewickNode(tree, node);
				if (child==null) 
					return null;
			}

			// prepare for next read operation
			readUntil( "):,;" );
			return node;
		} else {
			// read leaf
			node = new Node();
			tree.add(node);
			node.id = tree.size()-1;
			if (parent != null)
				parent.addChild(node);

			// Read name
			if ( readUntil( ":)," ) == '\0'  )
				return null;
			token = char1 + token;
			node.name = token;
			return node;
		}
	}
	
	// Scanning through the newick string to extract the leaves name
	public static ArrayList<String> scannwStr(String nwStr) {
		ArrayList <String> leaves = new ArrayList<String>();
		StringTokenizer st = new StringTokenizer(nwStr, "(),"); 
		String temp; 
		while (st.hasMoreTokens() ) {
			temp = st.nextToken();
			if(temp.equals(";") ) 
				break;
			leaves.add(temp);
		}
		return leaves;
	}
	
	// Parse each newick tree string into splits and record them in the given splitMap
	public static void parseNwStr(String nwStr,  Double[][] distanceMatrix) {
		pos = 0;
		depth = 0;
		token = new String();
		treeStr = new String(nwStr);

		ArrayList<Node> tree = new ArrayList<Node>(); 
		readNewickNode(tree, null);
		
	    for(int i=0; i<tree.size() ; i++ ) {
	    	Node n = tree.get(i);
	    	System.out.println(n.name);
	    }
	}
		
	//Recursive method to get 
	static void getSplit(Node node, ArrayList<Integer> list) {
		for (int i=0; i<node.nchildren; i++) {
			Node child = node.children.get(i);
			if (child.nchildren == 0) 
				list.add(name2id.get(child.name));
			else 
				getSplit(child,list);
		}
	}
	
	// This method will prune the splits for small size
	// For example: 
	// [0, 1, 2, 3, 8, 9, 10] U [4, 5, 6, 7, 11] -> [4, 5, 6, 7, 11]
	// For equal size split it will prefer split with starting indicies
	// For example: 
	// [0, 1, 2, 3, 10, 11] U [4, 5, 6, 7, 8, 9] -> [0, 1, 2, 3, 10, 11] 
	static void pruneMap(HashMap<List<Integer>,Integer> splitMap) {
		HashMap<List<Integer>,Integer> prunedMap = new HashMap<List<Integer>,Integer>();
		
		for(List<Integer> oldlist: splitMap.keySet()) {
			ArrayList<Integer> newlist = new ArrayList<Integer>();
			for(Integer id:id2name.keySet()) {
				if(!oldlist.contains(id))
					newlist.add(id);
			}
			Collections.sort(newlist);
		   
			if (splitMap.containsKey(newlist)) {
				if( newlist.size() < oldlist.size()) 
					prunedMap.put(newlist, splitMap.get(newlist) + splitMap.get(oldlist));
				else if (newlist.size() > oldlist.size()) 
					prunedMap.put(oldlist, splitMap.get(newlist) + splitMap.get(oldlist));
				else if (newlist.get(0) < oldlist.get(0))		
					prunedMap.put(newlist, splitMap.get(newlist) + splitMap.get(oldlist));
				else
					prunedMap.put(oldlist, splitMap.get(newlist) + splitMap.get(oldlist));					
			} else {
				if (newlist.size() < oldlist.size()) 
					prunedMap.put(newlist, splitMap.get(oldlist));
				else if (newlist.size() > oldlist.size()) 
					prunedMap.put(oldlist, splitMap.get(oldlist));
				else if(newlist.get(0) < oldlist.get(0))		
					prunedMap.put(newlist, splitMap.get(oldlist));
				else
					prunedMap.put(oldlist, splitMap.get(oldlist));
			}
		}
		
		splitMap.clear();
		splitMap.putAll(prunedMap);
	}
	
	//This method will show the mapping of splits and their respective frequencies
	public static void show(HashMap<List<Integer>,Integer> splitMap) {
		System.out.println("----------------------------------------------");
		for (List<Integer> key : splitMap.keySet()) 
			System.out.println(key.toString() + " : " + splitMap.get(key));

	}
}

class Node {
	public int id;                      // Node id (matches index in tree.nodes)
	public String name;                 // Node name (used mainly for leaves only)
	public Node parent;                 // Parent of this node 
	public int nchildren;               // Number of children
	public ArrayList<Node> children;    // Array of child pointers (size = nchildren)
	public double distance;
	//Default constructor
	public Node() {
		parent = null;
		nchildren = 0;
		id = -1;
		distance = 100;
		children = new ArrayList<Node>(); 
	}
	// Adds a child node
	public void addChild(Node node) {
		nchildren++;
		children.add(node);
		node.parent = this;
	}
	
	public void setDistanceToparent(double dist) {
		distance = dist;
	}
}
